import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:dart_style/dart_style.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';
// ignore: implementation_imports
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:path/path.dart' as p;
import 'lib/annotations/route_annot.dart';

void main() async {
  DateTime lastTime = DateTime.now();
  String path = '';
  final String absPath = p.join(p.current, path);
  final List<FileSystemEntity> file = scanDartFileByPath(absPath);
  print("扫描完成，耗时:${DateTime.now().difference(lastTime).inMilliseconds}ms");
  lastTime = DateTime.now();
  final RouteParser parser = RouteParser(files: file, path: absPath);
  parser.parser();
  print("解析完成，耗时:${DateTime.now().difference(lastTime).inMilliseconds}ms");
  lastTime = DateTime.now();
  parser.generate();
  print("代码生成完成，耗时:${DateTime.now().difference(lastTime).inMilliseconds}ms");
}

class RouteParser {
  //扫描的文件夹路径
  final String path;

  //dart文件的地址
  final List<FileSystemEntity> files;

  RouteParser({required this.files, required this.path});
  final List<RouteInfo> routeInfo = [];
  void parser() {
    for (int i = 0; i < files.length; i++) {
      final CompilationUnit astRoot;
      try {
        astRoot = parseFile(
          path: files[i].path,
          featureSet: FeatureSet.latestLanguageVersion(),
        ).unit;
      } catch (e) {
        log("文件解析异常，自动跳过:${files[i].path}");
        continue;
      }

      for (final CompilationUnitMember clazz in astRoot.declarations) {
        if (clazz is ClassDeclaration && clazz.metadata.isNotEmpty) {
          for (final Annotation annotation in clazz.metadata) {
            //查找ARoute注解
            if (annotation.name.name != typeOf<ARoute>().toString()) {
              continue;
            }
            //解析ARoute注解
            final NodeList<Expression> expressions =
                annotation.arguments!.arguments;
            String name = '';
            String path = '';
            String description = '';
            List<String> bindings = [];
            List<String> middleWare = [];
            Map<String, dynamic>? arguments;
            for (final Expression expression in expressions) {
              if (expression is NamedExpression) {
                final String key = expression.name.label.name;
                switch (key) {
                  case "name":
                    name = (expression.expression as SimpleStringLiteral).value;
                    break;
                  case "path":
                    path = (expression.expression as SimpleStringLiteral).value;
                    break;
                  case "description":
                    description =
                        (expression.expression as SimpleStringLiteral).value;
                    break;
                  case "bindings":
                    final value = expression.expression as ListLiteralImpl;
                    bindings = value.elements
                        .map((element) => element as SimpleIdentifier)
                        .map((e) => e.name)
                        .toList();
                    break;
                  case "middleWare":
                    final value = expression.expression as ListLiteralImpl;
                    middleWare = value.elements
                        .map((element) => element as SimpleIdentifier)
                        .map((e) => e.name)
                        .toList();
                    break;
                  case "arguments":
                    if (expression.expression is! SetOrMapLiteral) break;
                    var data = jsonDecode(expression.expression.toSource());
                    arguments = data;
                    break;
                  default:
                    break;
                }
              }
            }

            String export = "";
            final String libPath = p.join(this.path, 'lib');
            print("Found class ${clazz.name.name}");
            export =
                p.relative(files[i].path, from: libPath).replaceAll('\\', '/');
            final RouteInfo routeInfo = RouteInfo(
              constructors:
                  clazz.members.whereType<ConstructorDeclaration>().toList(),
              className: clazz.name.name,
              name: name,
              path: path,
              describe: description,
              bindings: bindings,
              midlleware: middleWare,
              export: export,
              arguments: arguments,
            );
            this.routeInfo.add(routeInfo);
          }
        }
      }
    }
  }

  Future<void> generate() async {
    _generateRoutes();
    _generateGetRoute();
  }

  ///生成路由表代码
  void _generateRoutes() {
    //
    final routesPath = p.join(path, "lib", "routes.dart");
    File routesFile = File(routesPath);
    if (routesFile.existsSync()) {
      routesFile.deleteSync();
    }
    routesFile.createSync();
    String emptyString = '';
    String code = routeInfo.map((e) => """
        ///
        /// 页面:[${e.className}]
        /// 路由名:${e.name}
        /// 路径:${e.path}
        /// 参数:${jsonEncode(e.arguments)}
        /// 描述:${e.describe}
        ///
        static const String ${e.name} = '${e.path}';\n
        static Future<T?>? to${e.name.substring(0, 1).toUpperCase()}${e.name.substring(1, e.name.length)}<T>(${e.arguments == null ? emptyString : "{${e.arguments!.keys.map((key) => '${e.arguments![key].runtimeType}? $key').join(',')}}"})
  {
    return Get.toNamed<T>(Routes.${e.name}${e.arguments == null ? emptyString : ",arguments:{${e.arguments!.keys.map((key) => "'$key':$key").join(',')}}"});
  }
        """).toList().join("\n\n  ");

    String codeTemp = '''
    part of 'route.dart';
    class Routes{
    $code
    }
    ''';
    routesFile.writeAsString(formatDart(codeTemp));
  }

  ///生成GetPage代码
  void _generateGetRoute() {
    final routesPath = p.join(path, "lib", "route.dart");
    File routesFile = File(routesPath);
    if (routesFile.existsSync()) {
      routesFile.deleteSync();
    }
    routesFile.createSync();
    StringBuffer imports = StringBuffer();
    imports.write("import 'package:get/get.dart';");
    imports.write("import 'package:flutter/material.dart';");
    for (var element in routeInfo) {
      imports.write("import '${element.export}';");
    }
    StringBuffer getPages = StringBuffer();
    for (var info in routeInfo) {
      String name = info.className;
      if (info.constructors.isNotEmpty &&
          info.constructors.first.name != null) {
        name = "${info.className}.${info.constructors.first.name}";
      }
      if (info.constructors.isNotEmpty &&
          info.constructors.first.constKeyword != null) {
        name = "const $name";
      }
      String bindings = info.bindings.isEmpty
          ? ''
          : 'bindings: [${info.bindings.map((e) => '$e()').join(',')}],';
      String middlewares = info.midlleware.isEmpty
          ? ''
          : 'middlewares: [${info.midlleware.map((e) => '$e()').join(',')}],';

      getPages.write("""
      GetPage(
      name: Routes.${info.name},
      curve: Curves.easeOutQuad,
      page: () => $name(),
      $bindings
      $middlewares
    ),\n
      """);
    }
    String codeTemplete = """
${imports.toString()}
part 'routes.dart';
class RouteConfig{
  static final List<GetPage> getPages = [${getPages.toString()}];
}  
  """;
    routesFile.writeAsString(formatDart(codeTemplete));
  }
}

Type typeOf<T>() => T;

///扫描指定包下的所有dart文件
List<FileSystemEntity> scanDartFileByPath(String absPath) {
  print("扫描地址:$absPath");
  final Directory directory = Directory(absPath);
  if (!directory.existsSync()) {
    throw "路径：$absPath [${directory.absolute.path}]不存在";
  }
  final List<FileSystemEntity> files = directory
      .listSync(recursive: true)
      .where((e) =>
          e.path.endsWith(".dart") &&
          e.statSync().type == FileSystemEntityType.file)
      .toList();
  print("文件数量:${files.length}");
  return files;
}

class RouteInfo {
  final String className;
  final List<ConstructorDeclaration> constructors;
  final String name;
  final String path;
  final String describe;
  final List<String> bindings;
  final List<String> midlleware;
  final String export;
  final Map<String, dynamic>? arguments;

  RouteInfo(
      {required this.constructors,
      required this.className,
      required this.name,
      required this.path,
      required this.describe,
      required this.bindings,
      required this.midlleware,
      required this.export,
      required this.arguments});
}

final DartFormatter _formatter = DartFormatter();

Future<void> formatFile(File? file) async {
  if (file == null) {
    return;
  }

  if (!file.existsSync()) {
    print('format error: ${file.absolute.path} doesn\'t exist\n');
    return;
  }

  processRunSync(
    executable: 'flutter',
    arguments: 'format ${file.absolute.path}',
    runInShell: true,
  );
}

String formatDart(String input) {
  try {
    return _formatter.format(input);
  } catch (e) {
    print(e);
  }
  return input;
}

void processRunSync({
  required String executable,
  required String arguments,
  bool runInShell = false,
}) {
  final ProcessResult result = Process.runSync(
    executable,
    arguments.split(' '),
    runInShell: runInShell,
  );
  if (result.exitCode != 0) {
    throw Exception(result.stderr);
  }
  print('${result.stdout}');
}
